package iterator.functions;

import function.unary.UnaryFunction;
import function.unary.UnaryPredicate;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 *
 * @author Shimu
 * @date 2-May-2013
 */
public class IteratorFunctions {

    /**
     * Given an Iterator over elements of type T1, returns a new Iterator
     * over elements of type T2. The elements in the new iterator are mapped
     * from the elements in the given iterator using the given mapper function.
     * WARNING: the methods of the given Iterator (hasNext, next, remove) 
     * should NOT be called upon after executing this method, otherwise the 
     * behavior of the returned Iterator is undefined.
     * 
     * @param <T1> the type of elements in the given Iterator
     * @param <T2> the type of elements in the returned Iterator
     * @param iter
     * @param mapper
     * @return 
     */
    public static <T1, T2> Iterator<T2> map(final Iterator<T1> iter,
            final UnaryFunction<T1, T2> mapper) {

        return new Iterator<T2>() {

            public boolean hasNext() {
                return iter.hasNext();
            }

            public T2 next() {
                if (iter.hasNext()) {
                    return mapper.evaluate(iter.next());
                } else {
                    throw new NoSuchElementException();
                }
            }

            public void remove() {
                iter.remove();
            }
        };
    }

    /**
     * Returns a new Iterator over the same elements as the given Iterator.
     * The new Iterator does not support the remove operation. WARNING: 
     * the methods of the given Iterator (hasNext, next, remove) 
     * should NOT be called upon after executing this method, otherwise the 
     * behavior of the returned Iterator is undefined.
     * 
     * @param <T> the type of elements in the given and returned Iterator
     * @param iter 
     * @return a new Iterator over the same elements as the given Iterator. 
     * The new Iterator does not support the remove operation.
     */
    public static <T> Iterator<T> immutable(final Iterator<T> iter) {
        return new Iterator<T>() {

            public boolean hasNext() {
                return iter.hasNext();
            }

            public T next() {
                return iter.next();
            }

            public void remove() {
                throw new UnsupportedOperationException(
                        "Removal not supported!");
            }
        };
    }

    /**
     * Returns a new Iterator over the elements of the given Iterator that
     * satisfy the given filter. Note the elements that satisfy the filter
     * is not pre-computed and stored in a list, it is computed during each
     * call to next(). This compromises speed but saves space. The remove
     * method of the returned Iterator is unsupported.
     * 
     * @param <T>
     * @param iter
     * @param filter
     * @return 
     */
    public static <T> Iterator<T> filter(final Iterator<T> iter,
            final UnaryPredicate<T> filter) {

        T temp = null;
        // Find the first element that satisfies the filter
        while (iter.hasNext()) {
            if (filter.evaluate(temp = iter.next())) {
                break;
            } else {
                temp = null;
            }
        }
        final T first = temp;

        return new Iterator<T>() {

            private T curr = first;

            public boolean hasNext() {
                return curr != null;
            }

            public T next() {
                T returnVal = curr;
                // Move onto the next element that satisfies the filter
                curr = null;
                while (iter.hasNext()
                        && !filter.evaluate(curr = iter.next())) {
                }
                return returnVal;
            }

            public void remove() {
                throw new UnsupportedOperationException();
                // iter.remove();
            }
        };
    }

    public static <T> void print(Iterator<T> iter) {
        System.out.print("[");
        if (iter.hasNext()) {
            System.out.print(iter.next());

            while (iter.hasNext()) {
                System.out.print(", ");
                System.out.print(iter.next());
            }
        }

        System.out.print("]");
    }
}
